package pl.ydp.components.mathcell.mathrecognizer.geometry.extractor
{
	import flash.geom.Point;
	import flash.geom.Rectangle;
	
	import pl.ydp.components.mathcell.mathrecognizer.engine.Parameters;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.Angle;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.AngleTrend;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.EuclideanVector;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.classification.CrawlType;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.classification.CurveType;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.classification.PrimitiveGeneralType;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.extractor.features.FeaturesCross;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.extractor.features.FeaturesLoop;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.extractor.features.LoopDiameter;
	import pl.ydp.components.mathcell.mathrecognizer.structures.Span;
	
	public class PrimitiveExtractor extends Object
	{
		public function PrimitiveExtractor(vectors:Array, task:Number = -1){
			
			if (task == -1){
				task = PrimitiveExtractorTask.ALL;
			}
			
			substances = new Array;
			
			var crossings:Array = FeaturesCross.findSelfCrossings(vectors);
			
			if (task & PrimitiveExtractorTask.LOOPS){
				extractLoops(vectors, crossings);
			}
			
			if (task & PrimitiveExtractorTask.WAVES){
				extractWaves(vectors);
			}
			
			if (task  & PrimitiveExtractorTask.CURVES){
				extractCurves(vectors);
			}
			
			if (task  & PrimitiveExtractorTask.LINES){
				extractLines(vectors);
			}
			
			if ((task  & PrimitiveExtractorTask.DOTS)  &&  substances.length == 0){
				extractDot(vectors);
			}
			
			
		}
		
		//------------------------------------------------------------------
		
		public var substances:Array;
		
		
		//------------------------------------------------------------------
		
		private function extractLoops(vectors:Array, crossings:Array):void{
			
			//loopBegins = new Array();
			//loopEnds = new Array();
			
			var v:Number,test:Number, test2:Number;
			
			var loopType:Number;
			var angleDiff:Number;
			var angleSum:Number;
			var angleSumNow:Number;
			var maxDiameter:LoopDiameter;
			var maxDiameterSatelliteIndex:Number;
			var secondDiameter:LoopDiameter;
			
			var testDist:Number;
			var closureDistance:Number
			
			var lastIndexWithProperAngle:Number
			var noiseCounter:Number;
			
			var conditionLength:Boolean;
			var conditionAngleSum:Boolean;
			var conditionDiameter:Boolean
			var conditionSecondDiameter:Boolean
			var conditionClosureDistance:Boolean;
			
			var processed:Boolean;
			var approved:Boolean;
			var finished:Boolean;
			var canceled:Boolean;
			
			var substanceCandidates:Array = new Array;
			
			const MAX_LOOP_ANGLE_STEP_DIFF:Number = AngleTrend.ANGLE_RIGHT*1.3;
			const MAX_LOOP_MARGIN:Number = AngleTrend.THRESHOLD_STILL;
			const MAX_LOOP_MARGIN_NOISE:Number = AngleTrend.THRESHOLD_STILL*2;
			
			const LOOP_NOISE_THRESHOLD:Number = 27;
			
			const SUM_NOW_COEFF:Number = 0.4;
			
			for (v = 0 ; v < vectors.length - 1 ; v ++ ){
				
				angleDiff = Angle.difference(vectors[v].angle, vectors[v+1].angle);
				angleSum = angleDiff;
				angleSumNow = angleDiff;
				maxDiameter = new LoopDiameter;
				secondDiameter = new LoopDiameter;
				maxDiameterSatelliteIndex = 0;
				
				if (Math.abs(angleDiff) < MAX_LOOP_ANGLE_STEP_DIFF){
					
					if (angleDiff > 0){
						loopType = CurveType.POSITIVE;
					} else if (angleDiff < 0){
						loopType = CurveType.NEGATIVE;
					} else {
						continue;
					}
					
					//loopBegins.push(v);
					substanceCandidates.push(new PrimitiveLoopSubstance(v,PrimitiveGeneralType.LOOP,loopType))
					
					conditionLength = false;
					conditionAngleSum = false;
					conditionClosureDistance = false;
					conditionDiameter = false;
					conditionSecondDiameter = false;
					
					approved = false;
					finished = false;
					canceled = false;
					lastIndexWithProperAngle = v;
					noiseCounter = 0;
					
					for (test = v+1 ; test < vectors.length - 1 ; test ++ ){
						
						if (Point.distance(vectors[test].point, vectors[test+1].point) > Parameters.MAX_VECTOR_LENGTH){
							continue;
						}
						
						if (test == 138){
							var assd:Number = 12;
						}
						
						angleDiff = Angle.difference(vectors[test].angle, vectors[test+1].angle);
						
						// CHECK ANGLES
						
						processed = false;
						
						if (loopType == CurveType.POSITIVE  &&  angleDiff < MAX_LOOP_ANGLE_STEP_DIFF  &&  angleDiff > -MAX_LOOP_MARGIN) {
							processed = true;
							if (angleDiff > 0){
								lastIndexWithProperAngle = test;
								noiseCounter = 0;
								angleSumNow = 0;
							} else if (angleDiff < 0){
								noiseCounter++;
								angleSumNow += angleDiff;
							}
						} else if (loopType == CurveType.NEGATIVE  &&  angleDiff > -MAX_LOOP_ANGLE_STEP_DIFF  &&  angleDiff < MAX_LOOP_MARGIN) {
							processed = true;
							if (angleDiff < 0){
								lastIndexWithProperAngle = test;
								noiseCounter = 0
								angleSumNow = 0;
							} else if (angleDiff > 0){
								noiseCounter++;
								angleSumNow += angleDiff;
							}
						} else if (Math.abs(angleDiff) < MAX_LOOP_MARGIN_NOISE){
							noiseCounter += LOOP_NOISE_THRESHOLD-1;
							processed = true;
						}
						
						if (processed  &&  lastIndexWithProperAngle != test  &&  
							(angleSumNow*loopType < -MAX_LOOP_MARGIN_NOISE  ||  noiseCounter > LOOP_NOISE_THRESHOLD)){
							processed = false;
						}
						
						if (processed){
							
							// FIND VALUES
						
							angleSum += angleDiff;
							angleSumNow = angleSumNow*(1-SUM_NOW_COEFF) + angleDiff*SUM_NOW_COEFF; 
							
							if (test - v > Parameters.LOOP_MIN_LENGTH/2  &&  Point.distance(vectors[v].point, vectors[test].point) > maxDiameter.length){
								maxDiameter = new LoopDiameter(vectors[v].point, vectors[test].point);
								maxDiameterSatelliteIndex = test;
							}
							
							// CHECK CONDITIONS
							
							if (!conditionLength  &&  test - v > Parameters.LOOP_MIN_LENGTH){
								conditionLength = true;
							}
							
							if (!conditionAngleSum  &&  conditionLength  &&  Math.abs(angleSum) > Parameters.LOOP_MIN_ANGLE_DIFF){
								conditionAngleSum = true;
							}
							
							if (!conditionDiameter  &&  conditionAngleSum  &&  maxDiameter.length > Parameters.LOOP_MIN_DIAMETER){
								conditionDiameter  = true;
							}
							
							if (conditionDiameter){
								
								closureDistance = Point.distance(vectors[v].point, vectors[test].point);
								
								if (closureDistance < Parameters.LOOP_MAX_CLOSURE_DISTANCE  ||  closureDistance < maxDiameter.length*Parameters.LOOP_MAX_CLOSURE_DISTANCE_DIAMETER_COEFF){
									conditionClosureDistance = true;
								} else {
									conditionClosureDistance = false;
								}
							}
							
							if (!conditionSecondDiameter  &&  conditionClosureDistance){
								
								secondDiameter = FeaturesLoop.findDiameterSecond(vectors, v, test, maxDiameterSatelliteIndex, maxDiameter.center);
															
								if (secondDiameter.length/maxDiameter.length > Parameters.LOOP_DIAMETERS_RATIO_MIN  &&  
									secondDiameter.length > Parameters.LOOP_DIAMETER_SECOND_MIN  &&  
									closureDistance < secondDiameter.length*Parameters.LOOP_MAX_CLOSURE_DISTANCE_DIAMETER2_COEFF){
									conditionSecondDiameter = true;
								}
							}  
							
							
							if (!approved  &&  conditionLength  &&  conditionAngleSum  &&  conditionDiameter  &&  conditionClosureDistance  &&  conditionSecondDiameter){
								approved = true;
							} else if (approved){
								 
								if (approved  &&  Point.distance(vectors[v].point, vectors[test].point) < Point.distance(vectors[v].point, vectors[test+1].point)){
									finished = true;
								}
							}
						
						}
						
						// BREAK TEST LOOP
						
						
						if (!processed  ||  test == vectors.length-2  ||  finished){
							
							if (finished  ||  approved){
								
								var loopBegin:Number = substanceCandidates[substanceCandidates.length-1].begin;
								var loopEnd:Number;
								
								if (test == vectors.length-2){
									loopEnd = test + 1;
								} else {
									loopEnd = test;
								}
								
								substanceCandidates[substanceCandidates.length-1].finish(loopEnd);
								
								// sprawdzanie pętli, w celu znalezienia wlasciwego miejsca zamkniecia petli
								
								var loopClosure:Span = FeaturesLoop.findLoopClosure(vectors, loopBegin, loopEnd);
								
								loopBegin = loopClosure.begin;
								loopEnd = loopClosure.end;
								
								substanceCandidates[substanceCandidates.length-1].begin = loopBegin;
								substanceCandidates[substanceCandidates.length-1].end = loopEnd;
								
								substanceCandidates[substanceCandidates.length-1].findFeaturesRelative(Point.distance(vectors[loopBegin].point, vectors[loopEnd].point) );
								
								// sprawdzenie dalszej czesci zioru punktow, w celu wyeliminowania przypadek 3
								
								if (!canceled){
									
									// erase beginning
									if (loopBegin-1 < (loopEnd - loopBegin)/4){
										for (test2 = loopBegin ; test2 < loopEnd ; test2 ++ ){
											
											if (Point.distance(vectors[0].point, vectors[test2].point) < Parameters.MAX_JOINT_DISTANCE){
												substanceCandidates.push( new PrimitiveSubstance(0, PrimitiveGeneralType.NONE, CurveType.NONE, loopBegin-1, 0) );
												break;
											}
											
										}
									}
									
									// erase ending
									if (vectors.length-1 - loopEnd < (loopEnd - loopBegin)/4){
										for (test2 = loopBegin ; test2 < loopEnd ; test2 ++ ){
											
											if (Point.distance(vectors[vectors.length-1].point, vectors[test2].point) < Parameters.MAX_JOINT_DISTANCE){
												substanceCandidates.push( new PrimitiveSubstance(loopEnd+1, PrimitiveGeneralType.NONE, CurveType.NONE, vectors.length-1, 0) );
												break;
											}
											
										}
									}
									
								}
								
								
							}
							/* MULTI-SEEK
							if ((finished || approved)  && !canceled){
								v = loopEnd;
							} else {
								v += Math.round(Parameters.MIN_LINE_LENGTH/4);
							}
							*/
							
							v += Parameters.EXTRACTOR_LOOP_SEEK_STEP; 
							
							break;
						}
						
						
					}  //  end for (test... 

					if (!substanceCandidates[substanceCandidates.length-1].isFinished()  ||  canceled){
						substanceCandidates.pop();
					}
				}
				
			} // end for (v...
			
			var currSubst:PrimitiveSubstance;
			var testSubst:PrimitiveSubstance;
			var overlap:Boolean;
			
			substanceCandidates = sortLoopSubstances(substanceCandidates);
			
		
			for each (currSubst in substanceCandidates){
				
				overlap = false;
				
				for each(testSubst in substances){
					if (!(currSubst.begin > testSubst.end  ||  currSubst.end < testSubst.begin)) {
						overlap = true;
					}
				}
				
				if (overlap){
					continue;
				}
				
				if (currSubst.length < Parameters.MIN_LINE_LENGTH){
					break;
				}
				substances.push(currSubst as PrimitiveSubstance);
				
			} 
			
		}
		
		

		//------------------------------------------------------------------
		
		private function sortLoopSubstances(subs:Array):Array{
			
			var j:Number;
			var k:Number;
			
			var tmp:PrimitiveSubstance;
			
			for (j = 0 ; j < subs.length ; j ++ ){
				for (k = 0 ; k < subs.length-1 ; k ++ ){
					
					
					if ((subs[k+1] is PrimitiveLoopSubstance  &&  subs[k] is PrimitiveSubstance)){
						
						tmp = subs[k+1];
						subs[k+1] = subs[k];
						subs[k] = tmp;
		
					} else if ((subs[k+1] is PrimitiveLoopSubstance  &&  subs[k] is PrimitiveLoopSubstance  &&
					(subs[k+1].lengthRelative + subs[k+1].closureDistanceRelative > subs[k].lengthRelative + subs[k].closureDistanceRelative))
					){
						
						tmp = subs[k+1];
						subs[k+1] = subs[k];
						subs[k] = tmp;
						
					} else if  (subs[k+1] is PrimitiveSubstance  &&  subs[k] is PrimitiveSubstance  &&
								subs[k+1].length > subs[k].length){
									
						tmp = subs[k+1];
						subs[k+1] = subs[k];
						subs[k] = tmp;
					}
					
				}
			}
			
			return subs;
			
		}
		
		//------------------------------------------------------------------
		
		private function extractWaves(vectors:Array):void{
			
			var v:Number, ref:Number, test:Number;
			
			var crawlDirection:Point = new Point;
			var currCrawlDirection:Point = new Point;
			var currCrawlDirectionPrevY:Number
			const CRAWL_POSITIVE:Number = 1;
			const CRAWL_NEGATIVE:Number = -1;
						
			var processed:Boolean;
			var approved:Boolean;  
			
			var lastIndexWithProperY:Number;
			var insideDeadArea:Boolean;
			var nextDeadAreaBeginIndex:Number;
			
			var mins:Array;
			var maxs:Array;
			
			var waveMin:Point ;
			var waveMax:Point ;
			var waveBegin:Point;
			var waveEnd:Point;
			var waveHeight:Number;
			
			var waveSizeRatio:Number;
			var waveAngle:Number;
			var waveAngleBeginning:Number;
			var waveAngleEnding:Number;
			var waveMeanPoint:Point;
			
			for (v = 0 ; v < vectors.length-1 ; v ++ ){
				
				if (vectors[v+1].point.x > vectors[v].point.x){
					crawlDirection.x = CrawlType.POSITIVE;
				} else if (vectors[v+1].point.x < vectors[v].point.x){
					crawlDirection.x = CrawlType.NEGATIVE;
				} else {
					crawlDirection.x = CrawlType.NONE;
				} 
				
				
				if (crawlDirection.x != CrawlType.NONE){
					
					// check DEAD areas
					
					insideDeadArea = false;
					
					for (test = 0 ; test < substances.length ; test ++ ){
						if (v >= substances[test].begin  &&  v <= substances[test].end){
							insideDeadArea = true;
							break;
						}
					}
					
					if (insideDeadArea){
						continue;
					}
					
					nextDeadAreaBeginIndex = vectors.length;
					
					for (test = 0 ; test < substances.length ; test ++ ){
						if (substances[test].begin > v  &&  substances[test].begin < nextDeadAreaBeginIndex){
							nextDeadAreaBeginIndex = substances[test].begin;
						}
					}
					
					// reset variables
					
					approved = false;
					
					waveBegin = vectors[v].point.clone();
					
					waveMin = new Point(0, Infinity);
					waveMax = new Point(0, -Infinity);
					
					mins = new Array;
					maxs = new Array;
					
					if (vectors[v+1].point.y > vectors[v].point.y){
						crawlDirection.y = CrawlType.POSITIVE;
					} else if (vectors[v+1].point.y < vectors[v].point.y){
						crawlDirection.y = CrawlType.NEGATIVE;
					} else {
						crawlDirection.y = CrawlType.NONE;
					} 
					
					currCrawlDirectionPrevY = crawlDirection.y;
					currCrawlDirection.y = crawlDirection.y;
					
					lastIndexWithProperY = NaN;
				
					
					for (test = v+1 ; test < vectors.length - 1 ; test ++ ){
						
						processed = false;
						
						if (vectors[test+1].point.x > vectors[test].point.x){
							currCrawlDirection.x = CrawlType.POSITIVE;
						} else if (vectors[test+1].point.x < vectors[test].point.x){
							currCrawlDirection.x = CrawlType.NEGATIVE;
						} else {
							currCrawlDirection.x = CrawlType.NONE;
						} 
						
						if ((currCrawlDirection.x == crawlDirection.x)  &&  test < nextDeadAreaBeginIndex){
							processed = true;
						} else {
							processed = false;
						}
							
						
						if (processed){
							
							// find crawl Y direction
							
							if (currCrawlDirection.y != CrawlType.NONE){
								currCrawlDirectionPrevY = currCrawlDirection.y;
							}
							
							if (vectors[test+1].point.y > vectors[test].point.y){
								currCrawlDirection.y = CrawlType.POSITIVE;
							} else if (vectors[test+1].point.y < vectors[test].point.y){
								currCrawlDirection.y = CrawlType.NEGATIVE;
							} else {
								currCrawlDirection.y = CrawlType.NONE;
							} 
							
							// find MIN & MAX
							
							if (currCrawlDirectionPrevY  == CrawlType.POSITIVE  &&  currCrawlDirection.y == CrawlType.NEGATIVE){
								maxs.push(test);
								if (vectors[test].point.y > waveMax.y){
									waveMax = vectors[test].point.clone(); 
									waveHeight = waveMax.y - waveMin.y;
								}
							} else if (currCrawlDirectionPrevY  == CrawlType.NEGATIVE  &&  currCrawlDirection.y == CrawlType.POSITIVE){
								mins.push(test);
								if (vectors[test].point.y < waveMin.y){
									waveMin = vectors[test].point.clone();
									waveHeight = waveMax.y - waveMin.y; 
								}
							}
							
							// find lastIndexWithProperY
							
							if (mins.length > 0  &&  maxs.length > 0){
								if (currCrawlDirection.y == CrawlType.NEGATIVE  && 
									vectors[test].point.y > waveMin.y - waveHeight*Parameters.WAVE_VERTICAL_TOLERANCE_COEFF){
									lastIndexWithProperY = test;
									
								} else if (currCrawlDirection.y == CrawlType.POSITIVE  && 
									vectors[test].point.y < waveMax.y + waveHeight*Parameters.WAVE_VERTICAL_TOLERANCE_COEFF){
									lastIndexWithProperY = test;
								}
								
							}						
							
						
						} else {
							
							break;
							
						}
						
					}  // end for (test...
					
					
					if (maxs.length > 0  &&  mins.length > 0  &&  lastIndexWithProperY){
						
						waveEnd = vectors[lastIndexWithProperY].point.clone();
						
						//var height:Number = waveMax.y - waveMin.y;	
						
						var beginPassed:Boolean = false;
						var endPassed:Boolean = false;
						var fieldRatioPassed:Boolean = false;		
						
						if (mins[0] < maxs[0]  &&  waveBegin.y < waveMax.y + waveHeight*Parameters.WAVE_VERTICAL_TOLERANCE_COEFF){
							beginPassed = true;
						}else if (maxs[0] < mins[0]  &&  waveBegin.y > waveMin.y - waveHeight*Parameters.WAVE_VERTICAL_TOLERANCE_COEFF){
							beginPassed = true;
						}	
						
						if (beginPassed){
							if (mins[mins.length-1] > maxs[maxs.length-1]  &&
								waveEnd.y < waveMax.y + waveHeight*Parameters.WAVE_VERTICAL_TOLERANCE_COEFF){
								endPassed = true;
							} else if (mins[mins.length-1] < maxs[maxs.length-1]  &&
								waveEnd.y > waveMin.y - waveHeight*Parameters.WAVE_VERTICAL_TOLERANCE_COEFF){
								endPassed = true;
							}
							
						}
						
						waveMeanPoint =  new Point( (vectors[mins[0]].point.x + vectors[maxs[0]].point.x)/2 , (vectors[mins[0]].point.y + vectors[maxs[0]].point.y)/2 );
						
						waveAngleBeginning = Angle.points2degrees(waveBegin, waveMeanPoint);
						waveAngleBeginning = Angle.differenceAbs90(waveAngleBeginning, 0);
						
						waveAngleEnding = Angle.points2degrees(waveMeanPoint, waveEnd);
						waveAngleEnding = Angle.differenceAbs90(waveAngleEnding, 0);
						
						waveSizeRatio = Math.abs(waveBegin.x - waveEnd.x)/waveHeight;
						
						if (waveSizeRatio > Parameters.WAVE_FIELD_COEFF_MIN  &&  waveSizeRatio < Parameters.WAVE_FIELD_COEFF_MAX  &&  
							waveAngleBeginning < Parameters.WAVE_ANGLE_MAX  &&  waveAngleEnding < Parameters.WAVE_ANGLE_MAX){
							fieldRatioPassed = true;
						}
						
						if (beginPassed  &&  endPassed  &&  fieldRatioPassed){
							substances.push( new PrimitiveSubstance(v, PrimitiveGeneralType.WAVE, CurveType.NONE, lastIndexWithProperY, 0) );
							approved = true;
						}
												
						if (waveBegin.y > waveMin.y - waveHeight*Parameters.WAVE_VERTICAL_TOLERANCE_COEFF  &&
							waveBegin.y < waveMax.y + waveHeight*Parameters.WAVE_VERTICAL_TOLERANCE_COEFF  &&
							waveEnd.y > waveMin.y - waveHeight*Parameters.WAVE_VERTICAL_TOLERANCE_COEFF  &&
							waveEnd.y < waveMax.y + waveHeight*Parameters.WAVE_VERTICAL_TOLERANCE_COEFF){
								
							
						}
						
					} 
					
					if (approved){
						v = lastIndexWithProperY;
					} else {
						v += Math.round(Parameters.MIN_LINE_LENGTH/2);
					}
					
					
				} //  end if 
				
				
			}  // end for (v...
			
			
		}
		
		//----------------------------------------------------------------------------------------
		

		private function extractCurves(vectors:Array):void{		
			
			var v:Number, test:Number;
			
			var nextDeadAreaBeginIndex:Number;
			
			var angleDiff:Number, angleDiffPrev:Number;
			var angleDeriv2nd:Number;
			var angleSum:Number;
			var currentCurveType:Number;
			var currentHelixCount:Number = 0;
			
			var noiseCounter:Number;
			var lastItemIndexWithoutNoise:Number;
			var angleMarginSum:Number;
			
			var insideDeadArea:Boolean;
			var processed:Boolean;
			var minCurveAngleReached:Boolean;
			
			const CURVE_NOISE_THRESHOLD:Number = 2;
			const CURVE_MARGIN:Number = 2;
			const CURVE_MARGIN_SUM_THRESHOLD:Number = AngleTrend.THRESHOLD_STILL/2;
						
			for (v = 0 ; v < vectors.length-1 ; v ++ ){
				
				angleDiff = Angle.difference(vectors[v].angle, vectors[v+1].angle);
				angleSum = 0;
				
				if (Math.abs(angleDiff) > Parameters.CURVE_ANGLE_STEP_MIN &&  Math.abs(angleDiff) < AngleTrend.THRESHOLD_CURVE){
					
					// CHECK SKIPPED AREAS
					
					insideDeadArea = false;
					
					for (test = 0 ; test < substances.length ; test ++ ){
						if (v >= substances[test].begin  &&  v <= substances[test].end){
							insideDeadArea = true;
							break;
						}
					}
					
					if (insideDeadArea){
						continue;
					}
					
					nextDeadAreaBeginIndex = vectors.length;
					
					for (test = 0 ; test < substances.length ; test ++ ){
						if (substances[test].begin > v  &&  substances[test].begin < nextDeadAreaBeginIndex){
							nextDeadAreaBeginIndex = substances[test].begin;
						}
					}
					
					
					if (angleDiff > 0){
						currentCurveType = CurveType.POSITIVE;
					} else if (angleDiff < 0) {
						currentCurveType = CurveType.NEGATIVE;
					} else {
						continue;
					}
					/*
					curveBegins.push(v);
					curveTypes.push(currentCurveType);
					curveHelixity.push(0);
					*/
					substances.push(new PrimitiveSubstance(v,PrimitiveGeneralType.CURVE, currentCurveType));
					
					noiseCounter = 0;
					angleMarginSum = 0;
					lastItemIndexWithoutNoise = v;
					minCurveAngleReached = false;
					currentHelixCount = 0;
					
					for (test = v+1 ; test < vectors.length - 1 ; test ++ ){
						
						angleDiffPrev = angleDiff;
						angleDiff = Angle.difference(vectors[test].angle, vectors[test+1].angle);
						angleDeriv2nd = Math.abs(angleDiffPrev - angleDiff);
						angleSum += angleDiff;
						
						// CHECK ANGLES FOR CURVE & HELIX
						
						if (!minCurveAngleReached  &&  Angle.differenceAbs(vectors[v].angle, vectors[lastItemIndexWithoutNoise].angle) > Parameters.CURVE_ANGLE_MIN){
							minCurveAngleReached = true;
						} else if (minCurveAngleReached  &&  Angle.differenceAbs(vectors[v].angle, vectors[lastItemIndexWithoutNoise].angle) < Parameters.HELIX_ANGLE_CATCH_DIFF_MIN){
							minCurveAngleReached = false;
							currentHelixCount++;
							//curveHelixity[curveHelixity.length-1] ++ ;
						}
						
						
						if (test == nextDeadAreaBeginIndex){
							
							processed = false;
							
						} else if (currentCurveType == CurveType.POSITIVE  &&  angleDiff > -CURVE_MARGIN  &&
						    angleDeriv2nd < Parameters.CURVE_ANGLE_STEP_DIFF_MAX  &&  Math.abs(angleDiff) < AngleTrend.THRESHOLD_CURVE){
								
							// continue POSITIVE curve
							if (angleDiff > 0){
								lastItemIndexWithoutNoise = test;
								angleMarginSum = 0;
							} else {
								angleMarginSum += angleDiff;
							}
							processed = true;
							
						} else if  (currentCurveType == CurveType.NEGATIVE  &&  angleDiff < CURVE_MARGIN  &&  
									angleDeriv2nd < Parameters.CURVE_ANGLE_STEP_DIFF_MAX  &&  Math.abs(angleDiff) < AngleTrend.THRESHOLD_CURVE){
										
							// continue NEGATIVE curve
							if (angleDiff < 0){
								lastItemIndexWithoutNoise = test;
								angleMarginSum = 0;
							} else {
								angleMarginSum += angleDiff;
							}
							processed = true;
							
						} else if  (noiseCounter < CURVE_NOISE_THRESHOLD  &&  
									Math.abs(angleDiff) < AngleTrend.THRESHOLD_CURVE){
										
							noiseCounter++;
							processed = true;
							
						} else {
							processed = false;	
						}
						
						if (Math.abs(angleMarginSum) > CURVE_MARGIN_SUM_THRESHOLD){
							processed = false;
						}
						
						if (test == 143){
							var asd:Number = 12;
						}
						if (test - v > Parameters.CURVE_BEND_ANGLE_LENGHT ){
							var bendAngleDiff:Number = Angle.differenceAbs(vectors[test - Parameters.CURVE_BEND_ANGLE_LENGHT].angle, vectors[test].angle);
						}
						
						if (test - v > Parameters.CURVE_BEND_ANGLE_LENGHT  &&  bendAngleDiff > Parameters.CURVE_BEND_ANGLE_DIFF_MAX){
							processed = false;
							lastItemIndexWithoutNoise = test - Parameters.CURVE_BEND_ANGLE_LENGHT;
						}
						
						if (!processed  ||  test == vectors.length-2){
							if (test == vectors.length-2  &&  processed){
								substances[substances.length-1].finish(test+1, currentHelixCount);
								//curveEnds.push(test+1);	
							} else {
								substances[substances.length-1].finish(lastItemIndexWithoutNoise, currentHelixCount);
								//curveEnds.push(lastItemIndexWithoutNoise);
							}
							v = lastItemIndexWithoutNoise;
							break;
						} 
						
					} //  end for test
					
					
					
					if (!substances[substances.length-1].isFinished()) {
						/*
						curveBegins.pop();
						curveTypes.pop();
						curveHelixity.pop();
						*/
						substances.pop();
						
					} else { // test loop was processed 
						
						angleDiff = Angle.differenceAbs(vectors[substances[substances.length-1].begin].angle, vectors[substances[substances.length-1].end].angle);
						
						//var angleHalf1_2:Number = Angle.points2degrees(vectors[curveBegins[curveBegins.length - 1]].point, vectors[Math.round((curveBegins[curveBegins.length - 1] + curveEnds[curveEnds.length - 1])/2)].point);
						//var angleHalf2_2:Number = Angle.points2degrees(vectors[Math.round((curveBegins[curveBegins.length - 1] + curveEnds[curveEnds.length - 1])/2)].point ,  vectors[curveEnds[curveEnds.length - 1]].point);
						
						var angleHalf1_2:Number = Angle.points2degrees(vectors[substances[substances.length-1].begin].point, vectors[Math.round((substances[substances.length-1].begin + substances[substances.length-1].end)/2)].point);
						var angleHalf2_2:Number = Angle.points2degrees(vectors[Math.round((substances[substances.length-1].begin + substances[substances.length-1].end)/2)].point ,  vectors[substances[substances.length-1].end].point);
						
						if ((substances[substances.length-1].end - substances[substances.length-1].begin < Parameters.CURVE_LENGTH_MIN)  ||  
							(!minCurveAngleReached  &&  currentHelixCount == 0)  ||  
							(Angle.differenceAbs(angleHalf1_2,angleHalf2_2) < Parameters.CURVE_ANGLE_MIN/2  &&  currentHelixCount == 0)
							) {
							/*
							curveBegins.pop();
							curveEnds.pop();
							curveTypes.pop();
							curveHelixity.pop();
							*/
							substances.pop();
							
						} else {
							var sdasd:Number = 10;
						}
					}
					
				}  //  end if (angleDiff < AngleTrend.THRESHOLD_CURVE)
				
			}  //  end for v
						
			
		}
		
		
		
		//----------------------------------------------------------------------------------------
		
		
		
		private function extractLines(vectors:Array):void{
			
			var lineBegins:Array = new Array();
			var lineEnds:Array = new Array();
			var lineLengths:Array = new Array();
			
			var v:Number, test:Number;
			
			var angleDiff:Number;
					
			var noiseCounter:Number;
			var lastItemIndexWithoutNoise:Number;
			var nextDeadAreaBeginIndex:Number;

			const MIN_LINE_LENGTH:Number = Parameters.MIN_LINE_LENGTH;
			const MAX_LINE_ANGLE:Number = AngleTrend.THRESHOLD_STILL*4;
			const LINE_NOISE_THRESHOLD:Number = 2;			
			
			var insideDeadArea:Boolean;
			
			for (v = 0 ; v < vectors.length-1 ; v ++ ){
				
				angleDiff = Angle.differenceAbs(vectors[v].angle,vectors[v+1].angle);
				
				if (angleDiff < MAX_LINE_ANGLE){
					
					insideDeadArea = false;
					
					for (test = 0 ; test < substances.length ; test ++){
						if (v >= substances[test].begin  &&  v <= substances[test].end){
							insideDeadArea = true;
							break
						}
					}	//  end for (test2...
					
					if (insideDeadArea){
						continue;
					}
					
					// find next dead area after v index
					
					nextDeadAreaBeginIndex = vectors.length;
					
					for (test = 0 ; test < substances.length ; test ++){
						if (substances[test].begin > v  &&  substances[test].begin < nextDeadAreaBeginIndex){
							nextDeadAreaBeginIndex = substances[test].begin;
						}
					}	//  end for (test2...
					
					lineBegins.push(v);
					noiseCounter = 0;
					
					lastItemIndexWithoutNoise = v+1;
					
					for (test = v+1 ; test < vectors.length ; test ++ ){
												
						angleDiff = Angle.differenceAbs(vectors[v].angle,vectors[test].angle);
						
						if (angleDiff < MAX_LINE_ANGLE  &&  test != vectors.length-1  &&  test != nextDeadAreaBeginIndex){
							// continue line
							lastItemIndexWithoutNoise = test;
						} else if (noiseCounter < LINE_NOISE_THRESHOLD   &&  angleDiff < AngleTrend.THRESHOLD_CURVE &&   test != vectors.length-1  &&  test != nextDeadAreaBeginIndex){
							noiseCounter++;
						} else {
							
							if (test == vectors.length-1){
								lineEnds.push(test);
							} else {
								lineEnds.push(lastItemIndexWithoutNoise);	
							}
							
							if (lineEnds[lineEnds.length - 1] - lineBegins[lineBegins.length - 1] > MIN_LINE_LENGTH){
								v += Math.floor(MIN_LINE_LENGTH/2);
							}
							
							break;							
						}
						
					} //  end for (test...
					
					if (lineBegins.length > lineEnds.length) {
						lineBegins.pop();
					} else if ( (lineEnds[lineEnds.length - 1] - lineBegins[lineBegins.length - 1] < MIN_LINE_LENGTH)) {
						lineBegins.pop();
						lineEnds.pop();
					} else {
						lineLengths.push(lineEnds[lineEnds.length - 1] - lineBegins[lineBegins.length - 1]);
					}
				
				}  //  end if (Angle.differenceAbs(vectors[v]...
				
			}  //  end for (v...
			
			
			
			// SORT LINES BY LENGTH
			
			
			var lineIndexesSorted:Array = new Array();
			var lineIndexesSelected:Array = new Array();
			var lineLengthsUnsotred:Array = new Array();
			var test2:Number;
			var tmp:Number;
			
			
			for each (tmp in lineLengths){
				lineIndexesSorted.push(lineIndexesSorted.length);
				lineLengthsUnsotred.push(tmp);
			}
			
			sortIndexesByLength(lineLengths, lineIndexesSorted, 0);
			
			// SELECT LINES
			
			var lineOverlap:Boolean;
			
			for (test = 0 ;  test < lineIndexesSorted.length ; test ++ ){
				
				lineOverlap = false;
				
				var refBegin:Number; 
				var refEnd:Number;
				var testBegin:Number = lineBegins[lineIndexesSorted[test]];
				var testEnd:Number = lineEnds[lineIndexesSorted[test]] ;
				
				var testBeginOriginal:Number = testBegin;
				var testEndOriginal:Number = testEnd;
				
				if (!lineOverlap  &&  testEnd - testBegin > MIN_LINE_LENGTH  &&  lineLengths[test] >= MIN_LINE_LENGTH){

					lineIndexesSelected.push(lineIndexesSorted[test]);
					
				} else if (lineLengths[test] < MIN_LINE_LENGTH){
					
					break;
				}
				
				
				// review line lengths, begins and ends
				
				refBegin = lineBegins[lineIndexesSelected[test]];
				refEnd = lineEnds[lineIndexesSelected[test]];
				
				for (test2 = test + 1 ; test2 < lineIndexesSorted.length ; test2 ++){
					
					testBegin = lineBegins[lineIndexesSorted[test2]];
					testEnd = lineEnds[lineIndexesSorted[test2]] ;
					
					if (!(testEnd < refBegin  ||  testBegin > refEnd)  &&  lineLengths[test2] >= MIN_LINE_LENGTH){
					
						if ((testBegin >= refBegin  &&  testBegin <= refEnd)  &&
							(testEnd >= refBegin  &&  testEnd <= refEnd)){
							// test line is contained by ref line
							// nothing to do
							testEnd = testBegin;
							lineOverlap = true;
						} else if (testBegin >= refBegin  &&  testBegin <= refEnd){
							// REF line contains only the beginning of TEST line
							testBegin = refEnd+1;
						} else if (testEnd >= refBegin  &&  testEnd <= refEnd){
							// REF line contains only the ending of TEST line
							testEnd = refBegin-1;
						}	
						
						lineBegins[lineIndexesSorted[test2]] = testBegin;
						lineEnds[lineIndexesSorted[test2]] = testEnd;
						lineLengths[test2] = testEnd - testBegin+1;
					}
					
				}  //  end for (test2...
				
				sortIndexesByLength(lineLengths, lineIndexesSorted, test+1);
				
				
			}  //  end for (test...		
			
			for (test = 0 ; test < lineIndexesSelected.length ; test ++ ){
				substances.push(new PrimitiveSubstance(lineBegins[lineIndexesSelected[test]], PrimitiveGeneralType.LINE, CurveType.NONE, lineEnds[lineIndexesSelected[test]]));
			}	
		}

		//------------------------------------------------------------------
		
		
		private function sortIndexesByLength(lengths:Array, indexes:Array, from:Number = 0):void{
			
			var a:Number, b:Number, tmp:Number;
			
			for (a = from ; a < lengths.length ; a ++){
				for (b = from ; b < lengths.length-1 ; b ++){
					if (lengths[b+1] > lengths[b]){
						tmp = lengths[b+1];
						lengths[b+1] = lengths[b];
						lengths[b] = tmp;
						
						tmp = indexes[b+1];
						indexes[b+1] = indexes[b];
						indexes[b] = tmp;
					}
				}	
			}		
					
		}		
		
		//------------------------------------------------------------------
		
		private function extractDot(vectors:Array):void{
			
			if (vectors.length == 0){
				return;
			}
			
			var field:Rectangle = new Rectangle(vectors[0].point.x, vectors[0].point.y, 0, 0);
			var vec:EuclideanVector;
			
			for each (vec in vectors){
				
				if (vec.point.x < field.x){
					field.width = field.right - vec.point.x; 
					field.x = vec.point.x; 
				}
				if (vec.point.y < field.y){
					field.height = field.bottom - vec.point.y;
					field.y = vec.point.y; 
				}
				if (vec.point.x > field.right){
					field.width = vec.point.x - field.x; 
				}
				if (vec.point.y > field.bottom){
					field.height = vec.point.y - field.y; 
				}
				
			}
			
			
			if (field.width < Parameters.DOT_MAX_SIZE  &&  field.height < Parameters.DOT_MAX_SIZE){
				substances.push(new PrimitiveSubstance(0, PrimitiveGeneralType.DOT, CurveType.NONE, vectors.length-1, 0) );
			}
			
			
		}
	}
}